#include "globals.h"
#include "errno.h"

#include "util/string.h"
#include "util/debug.h"

#include "mm/mmobj.h"
#include "mm/pframe.h"
#include "mm/mm.h"
#include "mm/page.h"
#include "mm/slab.h"
#include "mm/tlb.h"

#include "vm/vmmap.h"
#include "vm/shadow.h"
#include "vm/shadowd.h"

#define SHADOW_SINGLETON_THRESHOLD 5

int shadow_count = 0; /* for debugging/verification purposes */
#ifdef __SHADOWD__
/*
 * number of shadow objects with a single parent, that is another shadow
 * object in the shadow objects tree(singletons)
 */
static int shadow_singleton_count = 0;
#endif

static slab_allocator_t *shadow_allocator;

static void shadow_ref(mmobj_t *o);
static void shadow_put(mmobj_t *o);
static int  shadow_lookuppage(mmobj_t *o, uint32_t pagenum, int forwrite, pframe_t **pf);
static int  shadow_fillpage(mmobj_t *o, pframe_t *pf);
static int  shadow_dirtypage(mmobj_t *o, pframe_t *pf);
static int  shadow_cleanpage(mmobj_t *o, pframe_t *pf);

static mmobj_ops_t shadow_mmobj_ops = {
        .ref = shadow_ref,
        .put = shadow_put,
        .lookuppage = shadow_lookuppage,
        .fillpage  = shadow_fillpage,
        .dirtypage = shadow_dirtypage,
        .cleanpage = shadow_cleanpage
};

/*
 * This function is called at boot time to initialize the
 * shadow page sub system. Currently it only initializes the
 * shadow_allocator object.
 */
void
shadow_init()
{
	shadow_allocator = slab_allocator_create("shadow", sizeof(mmobj_t));
	KASSERT(NULL != shadow_allocator);
	dbg(DBG_TEST, "KASSERT PASS\n");
}

/*
 * You'll want to use the shadow_allocator to allocate the mmobj to
 * return, then then initialize it. Take a look in mm/mmobj.h for
 * macros which can be of use here. Make sure your initial
 * reference count is correct.
 */
mmobj_t *
shadow_create()
{
	mmobj_t *new_shadow = slab_obj_alloc(shadow_allocator);
	if (new_shadow) {
		new_shadow->mmo_ops = &shadow_mmobj_ops;
		new_shadow->mmo_refcount = 1;
		new_shadow->mmo_nrespages = 0;
		list_init(&new_shadow->mmo_respages);
		new_shadow->mmo_un.mmo_bottom_obj = NULL;
		new_shadow->mmo_shadowed = NULL;
	}
	return new_shadow;
}

/* Implementation of mmobj entry points: */

/*
 * Increment the reference count on the object.
 */
static void
shadow_ref(mmobj_t *o)
{
	KASSERT(o && (0 < o->mmo_refcount) && (&shadow_mmobj_ops == o->mmo_ops));
	dbg(DBG_TEST, "KASSERT PASS\n");
	o->mmo_refcount++;
}

/*
 * Decrement the reference count on the object. If, however, the
 * reference count on the object reaches the number of resident
 * pages of the object, we can conclude that the object is no
 * longer in use and, since it is a shadow object, it will never
 * be used again. You should unpin and uncache all of the object's
 * pages and then free the object itself.
 */
static void
shadow_put(mmobj_t *o)
{
	KASSERT(o && (0 < o->mmo_refcount) && (&shadow_mmobj_ops == o->mmo_ops));
	dbg(DBG_TEST, "KASSERT PASS\n");

	pframe_t *temp_pframe = NULL;
	
	if (o->mmo_refcount == (o->mmo_nrespages + 1)) {
		if (o->mmo_shadowed != NULL) {
			o->mmo_shadowed->mmo_ops->put(o->mmo_shadowed);
		}
		while (!list_empty(&o->mmo_respages)) {
			temp_pframe = list_head(&o->mmo_respages, pframe_t, pf_olink);
			KASSERT(!pframe_is_busy(temp_pframe));
			pframe_clean(temp_pframe);
			pframe_free(temp_pframe);
		}
	}
	o->mmo_refcount--;
	if (o->mmo_refcount == 0) {
		KASSERT(o->mmo_nrespages == 0);
		slab_obj_free(shadow_allocator, o);
	}
	dbgq(DBG_THR, "shadow_put(): leave\n");
}

/* This function looks up the given page in this shadow object. The
 * forwrite argument is true if the page is being looked up for
 * writing, false if it is being looked up for reading. This function
 * must handle all do-not-copy-on-not-write magic (i.e. when forwrite
 * is false find the first shadow object in the chain which has the
 * given page resident). copy-on-write magic (necessary when forwrite
 * is true) is handled in shadow_fillpage, not here. */
static int
shadow_lookuppage(mmobj_t *o, uint32_t pagenum, int forwrite, pframe_t **pf)
{
	/* do we need to traverse the shadow object chain? */
	dbgq(DBG_THR, "shadow_lookuppage(): enter\n");
	KASSERT(o);
	KASSERT(pf != NULL);

	mmobj_t *temp_mmobj = NULL;
	pframe_t *temp_pframe = NULL;

	temp_mmobj = o;
	while (temp_mmobj != NULL) {
	dbgq(DBG_THR, "shadow_lookuppage(): look up in shadow\n");
		if (!list_empty(&temp_mmobj->mmo_respages)) {
			list_iterate_begin(&temp_mmobj->mmo_respages, temp_pframe, pframe_t, pf_olink) {
				if (pagenum == temp_pframe->pf_pagenum) {
					*pf = temp_pframe;
					return 0;
				}
			}list_iterate_end();
		}

		if (forwrite == 1) {
			return -EINVAL;
		}
		/* can not find in this shadow obj, 
		 *try to find in next shadow object in chain
		 */
		temp_mmobj = temp_mmobj->mmo_shadowed;
	}

	/* we cannot find the pf */
	return -EINVAL;
}

/* As per the specification in mmobj.h, fill the page frame starting
 * at address pf->pf_addr with the contents of the page identified by
 * pf->pf_obj and pf->pf_pagenum. This function handles all
 * copy-on-write magic (i.e. if there is a shadow object which has
 * data for the pf->pf_pagenum-th page then we should take that data,
 * if no such shadow object exists we need to follow the chain of
 * shadow objects all the way to the bottom object and take the data
 * for the pf->pf_pagenum-th page from the last object in the chain). */
static int
shadow_fillpage(mmobj_t *o, pframe_t *pf)
{
	/* which one is dest, and which one is src */
	/* now, I assume o is scr, pf is dest */
	/* this is correct */
	KASSERT(pframe_is_busy(pf));
	KASSERT(!pframe_is_pinned(pf));
	dbg(DBG_TEST, "KASSERT PASS\n");

	int ret = 0;
	pframe_t *src_pframe = NULL;
	
	ret = shadow_lookuppage(o, pf->pf_pagenum, 0, &src_pframe);
	if (ret < 0) {
		return -EINVAL;
	}

	if (pf->pf_addr == memcpy(pf->pf_addr, src_pframe->pf_addr, PAGE_SIZE)) {
		return 0;
	}

	return -EINVAL;
}

/* These next two functions are not difficult. */

static int
shadow_dirtypage(mmobj_t *o, pframe_t *pf)
{
	/* since this is a shadow obj, all the pages in this shadow are created as 
	 * a dirty page, if we want to continue dirty it, we do nothing
	 */
	KASSERT(o);
	KASSERT(pf);

	return 0;
}

static int
shadow_cleanpage(mmobj_t *o, pframe_t *pf)
{
	KASSERT(o);
	KASSERT(pf);

	int ret = 0;
	pframe_t *dest_pframe = NULL;
	
	ret = pframe_lookup(o, pf->pf_pagenum, 1, &dest_pframe);
	if (ret < 0) {
		return -EINVAL;
	}

	if (dest_pframe->pf_addr == memcpy(dest_pframe->pf_addr, pf->pf_addr, PAGE_SIZE)) {
		return 0;
	}

	return -EINVAL;
}
